package xhl.blog.utils;

import cn.hutool.core.thread.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.kohsuke.github.*;
import xhl.blog.service.ThemeService;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.*;

/**
 * GithubUtils send request to api.github.com
 *
 * @author bigbang019
 * @date 2020-05-31
 */
@Slf4j
public class GithubUtils {

    /**
     * The prefix need to remove
     */
    static final String PREFIX = "https://github.com/";

    /**
     * PERIOD of time (ms)
     */
    private static final long PERIOD = 10 * 1000;

    /**
     * the thread
     */
    private static ScheduledThreadPoolExecutor pool;

    /**
     * Get latest release
     *
     * @param uri repository url must not be null
     * @return the map object containning tagname and zipfile url
     */
    public static Map<String, Object> getLatestRelease(String uri) {
        String repoUrl = StringUtils.removeStartIgnoreCase(uri, PREFIX);

        try {
            GithubLatestRelease githubLatestRelease = new GithubLatestRelease(repoUrl);

            ThreadFactory factory = new ThreadFactoryBuilder().setNamePrefix("Github-Latest-Release-").build();
            pool = new ScheduledThreadPoolExecutor(1, factory);
            pool.schedule(githubLatestRelease, PERIOD, TimeUnit.MILLISECONDS);
            return githubLatestRelease.result;
        } catch (Exception e) {
            log.warn("Interrupted", e);
            pool.shutdown();
        } finally {
            pool.shutdown();
        }
        return null;
    }

    /**
     * Get release information
     *
     * @param uri repository url must not be null
     * @return list of tagname of releases
     */
    public static List<String> getReleases(String uri) {
        String repoUrl = StringUtils.removeStartIgnoreCase(uri, PREFIX);

        try {
            GithubReleases githubReleases = new GithubReleases(repoUrl);

            ThreadFactory threadFactory = new ThreadFactoryBuilder().setNamePrefix("Github-Release-List-").build();
            pool = new ScheduledThreadPoolExecutor(1, threadFactory);
            pool.schedule(githubReleases, PERIOD, TimeUnit.MILLISECONDS);
            return githubReleases.result;
        } catch (Exception e) {
            log.warn("Interrupted", e);
            pool.shutdown();
        } finally {
            pool.shutdown();
        }
        return null;
    }

    /**
     * Get release information
     *
     * @param uri     repository url must not be null
     * @param tagName tag must not be null
     * @return the map object containning tagname and zipfile url
     */
    public static Map<String, Object> getRelease(String uri, String tagName) {
        String repoUrl = StringUtils.removeStartIgnoreCase(uri, PREFIX);

        try {
            GithubRelease githubRelease = new GithubRelease(repoUrl, tagName);

            ThreadFactory threadFactory = new ThreadFactoryBuilder().setNamePrefix("Github-Release-List-").build();
            pool = new ScheduledThreadPoolExecutor(1, threadFactory);
            pool.schedule(githubRelease, PERIOD, TimeUnit.MILLISECONDS);
            return githubRelease.result;
        } catch (Exception e) {
            log.warn("Interrupted", e);
            pool.shutdown();
        } finally {
            pool.shutdown();
        }
        return null;
    }

    /**
     * Get the content of theme.yaml/theme.yml
     *
     * @param uri    repository url must not be null
     * @param branch branch must not be null
     * @return content of the file
     */
    public static String accessThemeProperty(String uri, String branch) {
        String repoUrl = StringUtils.removeStartIgnoreCase(uri, PREFIX);

        try {
            GithubFile githubFile = new GithubFile(repoUrl, branch);

            ThreadFactory threadFactory = new ThreadFactoryBuilder().setNamePrefix("Github-File-").build();
            pool = new ScheduledThreadPoolExecutor(1, threadFactory);
            pool.schedule(githubFile, PERIOD, TimeUnit.MILLISECONDS);
            return githubFile.result;
        } catch (Exception e) {
            log.warn("Interrupted", e);
            pool.shutdown();
        } finally {
            pool.shutdown();
        }

        return null;
    }

    private static class GithubRelease implements Runnable {

        /**
         * The return result is zip url and tag name etc.
         */
        private HashMap<String, Object> result;

        /**
         * should be in format of "username/reponame"
         */
        private final String repoUrl;

        private final String tagName;

        public GithubRelease(String repoUrl, String tagName) {
            this.repoUrl = repoUrl;
            this.tagName = tagName;
            result = null;
        }


        @Override
        public void run() {
            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
            executor.scheduleAtFixedRate(() -> {
                try {
                    GitHub gitHub = GitHub.connectAnonymously();
                    GHRepository ghRepository = gitHub.getRepository(repoUrl);
                    List<GHRelease> ghReleaseList = ghRepository.listReleases().withPageSize(5).asList();

                    if (ghReleaseList.size() == 0) {
                        executor.shutdown();
                    }

                    Optional<GHRelease> res = ghReleaseList.stream()
                            .filter(release -> StringUtils.equalsIgnoreCase(release.getTagName(), tagName))
                            .findFirst();

                    if (res.isPresent()) {
                        GHRelease ghRelease = res.get();

                        result = new HashMap<String, Object>(5) {
                            {
                                put(ThemeService.ZIP_FILE_KEY, ghRelease.getZipballUrl());
                                put(ThemeService.TAG_KEY, ghRelease.getTagName());
                            }
                        };
                    }
                    executor.shutdown();
                } catch (Exception e) {
                    if (e instanceof HttpException) {
                        int code = ((HttpException) e).getResponseCode();
                        if (code != -1) {
                            executor.shutdown();
                        }
                    } else {
                        executor.shutdown();
                    }
                }
            }, 0, 2000, TimeUnit.MILLISECONDS);
        }
    }

    private static class GithubReleases implements Runnable {

        private List<String> result;

        private final String repoUrl;

        public GithubReleases(String repoUrl) {
            this.repoUrl = repoUrl;
            result = null;
        }

        @Override
        public void run() {
            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
            executor.scheduleAtFixedRate(() -> {
                try {
                    GitHub gitHub = GitHub.connectAnonymously();
                    GHRepository ghRepository = gitHub.getRepository(repoUrl);
                    List<GHRelease> ghReleaseList = ghRepository.listReleases().withPageSize(5).asList();

                    result = new ArrayList<>();

                    for (GHRelease ghRelease : ghReleaseList) {
                        result.add(ghRelease.getTagName());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    executor.shutdown();
                }
            }, 0, 2000, TimeUnit.MILLISECONDS);

        }
    }

    private static class GithubLatestRelease implements Runnable {

        /**
         * The return result is zip url and tag name etc.
         */
        private HashMap<String, Object> result;

        /**
         * should be in format of "username/reponame"
         */
        private final String repoUrl;

        public GithubLatestRelease(String repoUrl) {
            this.repoUrl = repoUrl;
            result = null;
        }

        @Override
        public void run() {
            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
            executor.scheduleAtFixedRate(() -> {
                try {
                    GitHub gitHub = GitHub.connectAnonymously();
                    GHRepository ghRepository = gitHub.getRepository(repoUrl);
                    List<GHRelease> ghReleaseList = ghRepository.listReleases().withPageSize(5).asList();

                    if (ghReleaseList.size() == 0) {
                        executor.shutdown();
                    }
                    GHRelease ghRelease = ghReleaseList.get(0);

                    result = new HashMap<String, Object>() {
                        {
                            put(ThemeService.ZIP_FILE_KEY, ghRelease.getZipballUrl());
                            put(ThemeService.TAG_KEY, ghRelease.getTagName());
                        }
                    };
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    executor.shutdown();
                }
            }, 0, 2000, TimeUnit.MILLISECONDS);
        }
    }

    private static class GithubFile implements Runnable {

        /**
         * result is file content
         */
        private String result;

        /**
         * should be in format of "username/repoName"
         */
        private final String repoUrl;

        /**
         * the branch name
         */
        private final String branch;

        public GithubFile(String repoUrl, String branch) {
            this.repoUrl = repoUrl;
            this.branch = branch;
            result = null;
        }

        @Override
        public void run() {
            ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
            executor.scheduleAtFixedRate(() -> {
                try {
                    GitHub gitHub = GitHub.connectAnonymously();
                    GHRepository ghRepository = gitHub.getRepository(repoUrl);
                    GHContent ghContent = null;
                    for (String themePropertyFile : ThemeService.THEME_PROPERTY_FILE_NAMES) {
                        ghContent = ghRepository.getFileContent(themePropertyFile, branch);
                    }
                    if (ghContent != null) {
                        InputStream stream = ghContent.read();
                        byte[] byteArr = new byte[stream.available()];
                        if (stream.read(byteArr) > 0) {
                            result = new String(byteArr);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    executor.shutdown();
                }
            }, 0, 2000, TimeUnit.MILLISECONDS);
        }
    }
}
